package it.unitn.testbed.server;

import it.unitn.testbed.client.TestbedService;
import it.unitn.testbed.client.common.BriefTaskComponent;
import it.unitn.testbed.client.common.MoteConfig;
import it.unitn.testbed.server.config.ConfigUser;
import it.unitn.testbed.server.config.TestbedConfig;
import it.unitn.testbed.server.task.SerialMsgUpload;
import it.unitn.testbed.server.task.TaskComponent;
import it.unitn.testbed.server.task.TaskScheduler;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 * 
 * This object is the central point of the server-side testbed service.
 * 
 * @author Stefan Guna
 * @see it.unitn.testbed.client.Testbed
 */
public class TestbedServiceImpl extends RemoteServiceServlet implements
		TestbedService, ConfigUser {
	public static Logger log = Logger.getLogger(TestbedServiceImpl.class
			.getName());
	private static final long serialVersionUID = 1L;

	/** Configuration of the testbed */
	private TestbedConfig config;

	/**
	 * The name of the configuration file used to save/load deployment data when
	 * the servlet is inited/destroyed. Loaded from the environmental variable
	 * TESTBED_CONFIG and falls back to {@literal web.xml}.
	 */
	private String testbedConfigFile;

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.GenericServlet#destroy()
	 */
	@Override
	public void destroy() {
		super.destroy();

		try {
			config.dumpConfigXml(new FileOutputStream(testbedConfigFile));
		} catch (Exception e) {
			log.error("Unable to dump XML configuration file:");
			for (StackTraceElement ste : e.getStackTrace())
				log.error(ste);
		}
		TestbedConfig.unregisterService(this);
	}

	private void fetchConfig(ServletConfig config) {
		testbedConfigFile = System.getenv("TESTBED_CONFIG");

		if (testbedConfigFile == null && config != null) {
			testbedConfigFile = config.getInitParameter("deployment-file");
			log
					.warn("Fallback to default deployment configuration file because TESTBED_CONFIG is not set.");
		}

		if (testbedConfigFile == null) {
			log
					.warn("TESTBED_CONFIG not set, proceeding with empty configuration");
			return;
		}
		try {
			FileInputStream in = new FileInputStream(testbedConfigFile);
			this.config = new TestbedConfig(in);
		} catch (FileNotFoundException e) {
			log.warn(testbedConfigFile
					+ " not found, proceeding with empty configuration.");
		} catch (Exception e) {
			log.error("Unable to parse deployment file:");
			log.error(e.getMessage());
			for (StackTraceElement ste : e.getStackTrace())
				log.error(ste);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.server.config.ConfigUser#getConfig()
	 */
	public TestbedConfig getConfig() {
		return config;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.client.TestbedService#getTaskComponents()
	 */
	public BriefTaskComponent[] getTaskComponents() {
		return internalGetTaskComponents();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.client.TestbedService#getTestbedConfig()
	 */
	public MoteConfig[] getTestbedConfig() {
		ArrayList<MoteConfig> motes = config.getMotes();
		if (motes == null)
			return new MoteConfig[0];
		MoteConfig result[] = new MoteConfig[motes.size()];
		return motes.toArray(result);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.GenericServlet#init()
	 */
	@Override
	public void init() throws ServletException {
		super.init();
		fetchConfig(null);
		TestbedConfig.registerService(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
	 */
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		fetchConfig(config);
		TestbedConfig.registerService(this);
	}

	@SuppressWarnings("unchecked")
	private BriefTaskComponent[] internalGetTaskComponents() {
		HttpServletRequest request = this.getThreadLocalRequest();
		HttpSession session = request.getSession(true);
		BriefTaskComponent result[] = new BriefTaskComponent[0];
		ArrayList<BriefTaskComponent> resultList = new ArrayList<BriefTaskComponent>();

		synchronized (session) {
			ArrayList<TaskComponent> taskComponents = (ArrayList<TaskComponent>) session
					.getAttribute("taskComponents");
			if (taskComponents == null)
				return null;
			for (TaskComponent taskComponent : taskComponents)
				resultList.add(new BriefTaskComponent(taskComponent.getId(),
						taskComponent.getFileName(), taskComponent.getMotes()));
		}
		result = resultList.toArray(result);
		log.trace("returning " + result.length + " components");
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.client.TestbedService#newTask()
	 */
	public void newTask() {
		HttpServletRequest request = this.getThreadLocalRequest();
		HttpSession session = request.getSession(true);

		session.removeAttribute("serialMsgs");
		session.removeAttribute("taskComponents");

		log.trace("Ready for a new task.");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.client.TestbedService#removeTaskComponent(long)
	 */
	@SuppressWarnings("unchecked")
	public BriefTaskComponent[] removeTaskComponent(long id) {
		HttpServletRequest request = this.getThreadLocalRequest();
		HttpSession session = request.getSession(true);

		synchronized (session) {
			ArrayList<TaskComponent> taskComponents = (ArrayList<TaskComponent>) session
					.getAttribute("taskComponents");
			if (taskComponents == null)
				return null;
			for (TaskComponent taskComponent : taskComponents) {
				if (taskComponent.getId() == id) {
					taskComponents.remove(taskComponent);
					log.trace("found component " + id + ", removing it");
					break;
				}
			}
		}
		return internalGetTaskComponents();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * it.unitn.testbed.server.config.ConfigUser#setConfig(it.unitn.testbed.
	 * server.config.TestbedConfig)
	 */
	public void setConfig(TestbedConfig config) {
		this.config = config;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see it.unitn.testbed.client.TestbedService#submitTask(int)
	 */
	@SuppressWarnings("unchecked")
	public Date submitTask(int duration) {
		HttpServletRequest request = this.getThreadLocalRequest();
		HttpSession session = request.getSession(true);

		ArrayList<TaskComponent> taskComponents = (ArrayList<TaskComponent>) session
				.getAttribute("taskComponents");
		HashMap<String, File> serialMsgs = (HashMap<String, File>) session
				.getAttribute("serialMsgs");

		if (serialMsgs != null)
			log.trace("Preparing for " + serialMsgs.size() + " msgs.");

		Date result = TaskScheduler.getScheduler().addTask(taskComponents,
				serialMsgs, duration, this);

		session.removeAttribute("serialMsgs");
		session.removeAttribute("taskComponents");
		SerialMsgUpload.removeSessionFileItems(request);

		return result;
	}
}
